package org.apache.maven.plugins.enforcer.utils;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import static org.apache.maven.plugins.enforcer.constant.EnforcerRuleType.BANNED_DEPENDENCIES;
import static org.apache.maven.plugins.enforcer.constant.EnforcerRuleType.REQUIRE_DEPENDENCIES;
import static org.apache.maven.plugins.enforcer.constant.EnforcerRuleType.REQUIRE_RELEASE_DEPS;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.plugins.enforcer.constant.EnforcerRuleType;
import org.apache.maven.plugins.enforcer.entity.RuleValidDetail;
import org.apache.maven.plugins.enforcer.entity.RuleValidResult;
import org.apache.maven.plugins.enforcer.entity.SimpleArtifact;
import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher.Pattern;
import org.apache.maven.shared.dependency.graph.DependencyNode;

/**
 * 
 * @author Robert Scholte
 * @since 3.0.0
 */
public final class ArtifactUtils
{
    private ArtifactUtils()
    {
    }

    public static Set<Artifact> getAllDescendants( DependencyNode node )
    {
        Set<Artifact> children = null;
        if ( node.getChildren() != null )
        {
            children = new HashSet<>();
            for ( DependencyNode depNode : node.getChildren() )
            {
                Artifact originArtifact = depNode.getArtifact( );
                if ( originArtifact instanceof DefaultArtifact )
                {
                    DefaultArtifactDecorator defaultArtifactDecorator = convertArtifactDecorator( originArtifact );
                    defaultArtifactDecorator.setParent( convertArtifactDecorator( node.getArtifact( ) ) );
                    children.add( defaultArtifactDecorator );
                }
                else
                {
                    children.add( originArtifact );
                }

                Set<Artifact> subNodes = getAllDescendants( depNode );
                if ( subNodes != null )
                {
                    children.addAll( subNodes );
                }
            }
        }
        return children;
    }

  private static DefaultArtifactDecorator convertArtifactDecorator( Artifact originArtifact )
  {
    DefaultArtifactDecorator defaultArtifactDecorator = new DefaultArtifactDecorator(
        originArtifact.getGroupId( ), originArtifact.getArtifactId( ),
        originArtifact.getVersionRange( ), originArtifact.getScope( ), originArtifact.getType( ),
        originArtifact.getClassifier( ), originArtifact.getArtifactHandler(),
        originArtifact.isOptional( ) );
    return defaultArtifactDecorator;
  }

  /**
     * Checks the set of dependencies against the list of patterns.
     * 
     * @param thePatternList the patterns
     * @param dependencies the dependencies
     * @return a set containing artifacts matching one of the patterns or <code>null</code>
     * @throws EnforcerRuleException the enforcer rule exception
     */
    public static Set<Artifact> checkDependencies( Set<Artifact> dependencies,
        List<String> thePatternList, Set<String> requireNotFounds, EnforcerRuleType enforcerRuleType )
        throws EnforcerRuleException
    {
        Set<Artifact> foundMatches = null;

        Set<SimpleArtifact> recordArtifacts = new HashSet<>( );

        if ( thePatternList != null && thePatternList.size() > 0 )
        {
            for ( String pattern : thePatternList )
            {
                String[] subStrings = pattern.split( ":" );
                subStrings = StringUtils.stripAll( subStrings );
                String resultPattern = StringUtils.join( subStrings, ":" );

                boolean notMatch = true;
                for ( Artifact artifact : dependencies )
                {
                    if ( compareDependency( resultPattern, artifact ) )
                    {
                        // only create if needed
                        if ( foundMatches == null )
                        {
                            foundMatches = new HashSet<>();
                        }
                        foundMatches.add( artifact );
                        notMatch = false;

                        if ( BANNED_DEPENDENCIES.equals( enforcerRuleType ) )
                        {
                            recordArtifact( recordArtifacts, resultPattern, artifact );
                        }
                        else if ( REQUIRE_RELEASE_DEPS.equals( enforcerRuleType ) && artifact.isSnapshot( ) )
                        {
                            recordArtifact( recordArtifacts, resultPattern, artifact );
                        }
                    }
                }
                if ( notMatch && REQUIRE_DEPENDENCIES.equals( enforcerRuleType ) )
                {
                    // add not match patterns
                    requireNotFounds.add( pattern );
                }
            }
        }

        writeResultFile( requireNotFounds, recordArtifacts, enforcerRuleType );

        return foundMatches;
    }

    private static void writeResultFile( Set<String> requireNotFounds, Set<SimpleArtifact> recordArtifacts,
        EnforcerRuleType  enforcerRuleType )
    {
        String ruleValidResultFile = System.getProperty( "rule.valid.result.file" );
        if ( null != ruleValidResultFile && ruleValidResultFile.length( ) != 0 )
        {
            RuleValidDetail bannedDependenciesRuleValidDetail = null;
            RuleValidDetail requireReleaseDepsRuleValidDetail = null;
            boolean isBannedDependencies = BANNED_DEPENDENCIES.equals( enforcerRuleType );
            boolean isRequireReleaseDeps = REQUIRE_RELEASE_DEPS.equals( enforcerRuleType );
            ObjectMapper objectMapper = new ObjectMapper( );
            objectMapper.setSerializationInclusion( Include.NON_NULL );
            try
            {
                File file = new File( ruleValidResultFile );
                if ( file.exists( ) )
                {
                    String lastDetail =  FileUtils.readFileToString( file,
                        StandardCharsets.UTF_8 );
                    if ( StringUtils.isNotEmpty ( lastDetail ) )
                    {
                        try
                        {
                            RuleValidResult validResult = objectMapper.readValue(
                                lastDetail, RuleValidResult.class );
                            if ( isBannedDependencies || isRequireReleaseDeps )
                            {
                                if ( isBannedDependencies )
                                {
                                    requireReleaseDepsRuleValidDetail = validResult.queryRuleValidDetail(
                                        REQUIRE_RELEASE_DEPS.getType( ) );
                                }
                                else
                                {
                                    bannedDependenciesRuleValidDetail = validResult.queryRuleValidDetail(
                                        BANNED_DEPENDENCIES.getType( ) );
                                }
                                Optional.ofNullable( validResult.queryRuleValidDetail(
                                        enforcerRuleType.getType( ) ) )
                                    .ifPresent( detail ->
                                        {
                                            if ( !detail.isSuccess( ) )
                                            {
                                                recordArtifacts.addAll( detail.getSimpleArtifacts( ) );
                                            }
                                        }
                                    );
                            }
                            else
                            {
                                bannedDependenciesRuleValidDetail = validResult.queryRuleValidDetail(
                                    BANNED_DEPENDENCIES.getType( ) );
                                requireReleaseDepsRuleValidDetail = validResult.queryRuleValidDetail(
                                    REQUIRE_RELEASE_DEPS.getType( ) );
                            }

                            Optional.ofNullable( validResult.queryRuleValidDetail(
                                    REQUIRE_DEPENDENCIES.getType( ) ) )
                                .ifPresent( detail ->
                                    {
                                        if ( !detail.isSuccess( ) )
                                        {
                                            Set<String> lastRequireNotFounds = detail.getSimpleArtifacts( )
                                                .stream( ).map( SimpleArtifact::getRule )
                                                .collect( Collectors.toSet( ) );
                                            requireNotFounds.retainAll( lastRequireNotFounds );
                                        }
                                    }
                                );
                        }
                        catch ( JsonProcessingException e )
                        {
                            e.printStackTrace( );
                            file.delete( );
                        }
                    }
                }

                RuleValidResult result = new RuleValidResult( );
                if ( null != bannedDependenciesRuleValidDetail )
                {
                    result.addRuleValidDetail( BANNED_DEPENDENCIES.getType(), bannedDependenciesRuleValidDetail );
                }
                if ( null != requireReleaseDepsRuleValidDetail )
                {
                    result.addRuleValidDetail( REQUIRE_RELEASE_DEPS.getType(), requireReleaseDepsRuleValidDetail );
                }
                if ( isBannedDependencies || isRequireReleaseDeps )
                {
                    RuleValidDetail ruleValidDetail = new RuleValidDetail( );
                    if ( recordArtifacts.size( ) > 0 )
                    {
                        ruleValidDetail.setSimpleArtifacts( recordArtifacts );
                        ruleValidDetail.setSuccess( false );
                        result.setSuccess( false );
                    }
                    else
                    {
                        ruleValidDetail.setSuccess( true );
                    }
                    result.addRuleValidDetail( enforcerRuleType.getType( ), ruleValidDetail );
                }


                RuleValidDetail requireDetail = new RuleValidDetail( );
                if ( !requireNotFounds.isEmpty( ) )
                {
                    requireDetail.setSuccess( false );
                    requireDetail.setSimpleArtifacts( requireNotFounds.stream( ).map( rule ->
                        {
                            SimpleArtifact artifact = new SimpleArtifact( );
                            artifact.setRule( rule );
                            return artifact;
                        }
                    ).collect( Collectors.toSet( ) ) );
                    result.setSuccess( false );
                }
                else
                {
                    requireDetail.setSuccess( true );
                }
                result.addRuleValidDetail( REQUIRE_DEPENDENCIES.getType( ), requireDetail );

                String resultJson =  objectMapper.writeValueAsString( result );
                FileUtils.writeStringToFile( file, resultJson, StandardCharsets.UTF_8 );
            }
            catch ( Throwable e )
            {
                e.printStackTrace( );
            }
        }
    }

    private static void recordArtifact( Set<SimpleArtifact> recordArtifacts, String resultPattern,
        Artifact artifact )
    {
        SimpleArtifact simpleArtifact = new SimpleArtifact( );
        simpleArtifact.setArtifactId( artifact.getArtifactId( ) );
        simpleArtifact.setGroupId( artifact.getGroupId( ) );
        simpleArtifact.setVersion( artifact.getVersion( ) );
        simpleArtifact.setRule( resultPattern );
        if ( artifact instanceof DefaultArtifactDecorator )
        {
            DefaultArtifactDecorator artifactDecorator =  ( DefaultArtifactDecorator ) artifact;
            DefaultArtifactDecorator parentArtifactDecorator = artifactDecorator.getParent( );
            SimpleArtifact currentSimpleArtifact = simpleArtifact;
            while ( null != parentArtifactDecorator )
            {
                SimpleArtifact parentSimpleArtifact =
                    buildSimpleArtifact( parentArtifactDecorator );
                currentSimpleArtifact.setBy( parentSimpleArtifact );
                parentArtifactDecorator = parentArtifactDecorator.getParent( );
                currentSimpleArtifact = parentSimpleArtifact;
            }
        }

        recordArtifacts.add( simpleArtifact );
    }

    private static SimpleArtifact buildSimpleArtifact( DefaultArtifactDecorator artifactDecorator )
    {
        SimpleArtifact simpleArtifact = new SimpleArtifact( );
        simpleArtifact.setArtifactId( artifactDecorator.getArtifactId( ) );
        simpleArtifact.setGroupId( artifactDecorator.getGroupId( ) );
        simpleArtifact.setVersion( artifactDecorator.getVersion( ) );
        return simpleArtifact;
    }

    /**
     * Compares the given pattern against the given artifact. The pattern should follow the format
     * <code>groupId:artifactId:version:type:scope:classifier</code>.
     * 
     * @param pattern The pattern to compare the artifact with.
     * @param artifact the artifact
     * @return <code>true</code> if the artifact matches one of the patterns
     * @throws EnforcerRuleException the enforcer rule exception
     */
    private static boolean compareDependency( String pattern, Artifact artifact )
        throws EnforcerRuleException
    {
    
        ArtifactMatcher.Pattern am = new Pattern( pattern );
        boolean result;
        try
        {
            result = am.match( artifact );
        }
        catch ( InvalidVersionSpecificationException e )
        {
            throw new EnforcerRuleException( "Invalid Version Range: ", e );
        }
    
        return result;
    }

}
